package cn.wizzer.app.user.modules.service.impl;

import cn.wizzer.app.sys.modules.services.SysParamService;
import cn.wizzer.app.user.modules.models.User;
import cn.wizzer.app.user.modules.models.UserAccount;
import cn.wizzer.app.user.modules.models.UserAccountRecord;
import cn.wizzer.app.user.modules.models.enums.BizTypeEnum;
import cn.wizzer.app.user.modules.models.vo.AccountRecordVo;
import cn.wizzer.app.user.modules.models.vo.WithdrawAndRechargeVO;
import cn.wizzer.app.user.modules.services.UserService;
import cn.wizzer.app.user.modules.services.account.UserAccountRecordService;
import cn.wizzer.app.user.modules.services.account.UserAccountService;
import cn.wizzer.framework.base.exceptions.BizException;
import cn.wizzer.framework.base.model.enums.BizExceptionEnum;
import cn.wizzer.framework.base.service.BaseServiceImpl;
import cn.wizzer.framework.base.service.CommonDBService;
import cn.wizzer.framework.page.Pagination;
import cn.wizzer.framework.util.Result;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.annotation.Service;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.Times;
import org.nutz.log.Log;
import org.nutz.log.Logs;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@IocBean(args = {"refer:dao"})
@Service(interfaceClass = UserAccountRecordService.class)
public class UserAccountRecordServiceImpl extends BaseServiceImpl<UserAccountRecord> implements UserAccountRecordService {

    private final static Log log = Logs.get();

    public UserAccountRecordServiceImpl(Dao dao) {
        super(dao);
    }

    @Inject
    private CommonDBService dbService;

    @Inject
    private UserAccountService accountService;

    @Inject
    @Reference
    private SysParamService sysParamService;

    @Inject
    private UserService userService;

    @Aop(TransAop.REPEATABLE_READ)
    @Override
    public void add(String currencyId, String userId, BigDecimal amount, BizTypeEnum bizType, String bizId, String bizNo) {
        log.info("userId:" + userId + "&currencyId:" + currencyId + " account remain change :" + amount);
        try {
            amount = verifyAmount(amount, bizType);
            UserAccount account = accountService.select(userId, currencyId);
            //当前余额
            BigDecimal currentRemain = amount.add(account.getBalance());
            if (BigDecimal.ZERO.compareTo(currentRemain) > 0) {
                Result.breakWith(BizExceptionEnum.INSUFFICIENT_BALANCE);
            }
            account.setBalance(currentRemain);
            insetRecord(currencyId, userId, bizType, bizId, bizNo, account, amount);
            //修改余额
            accountService.update(account);
        } catch (BizException be) {
            log.error(be.getMessage());
            throw be;
        } catch (Exception e) {
            log.error(e.getMessage());
            Result.breakWith(BizExceptionEnum.ABNORMAL);
        }
    }

    private BigDecimal verifyAmount(BigDecimal amount, BizTypeEnum bizType) {
        //向下取保留两位小数
        amount = amount.setScale(2, RoundingMode.DOWN);

        if (BigDecimal.ZERO.compareTo(amount) >= 0) {
            Result.breakWith(BizExceptionEnum.ENSURE_BEHAVIORAL_SAFETY);
        }
        //支出项
        if (bizType.VALUE < 0) {
            //取反
            amount = amount.negate();
        }
        return amount;
    }


    private void insetRecord(String currencyId, String userId, BizTypeEnum bizType, String bizId, String bizNo, UserAccount account, BigDecimal amount) {
        UserAccountRecord userAccountRecord = new UserAccountRecord();
        userAccountRecord.setCtAt(System.currentTimeMillis());
        userAccountRecord.setUserId(userId);
        userAccountRecord.setBizId(bizId);
        userAccountRecord.setBizNo(bizNo);
        userAccountRecord.setBizType(bizType);
        userAccountRecord.setBalance(account.getBalance());
        userAccountRecord.setAmount(amount);
        userAccountRecord.setCurrencyId(currencyId);
        this.insert(userAccountRecord);
    }

    @Override
    public Pagination listPage(Integer pageNumber, int pageSize, String keyword, BizTypeEnum bizType, String[] searchDate, String currencyId) {
        Sql sql = Sqls.create("SELECT r.id, r.userId, r.amount, r.balance, r.bizNo, " +
                "r.bizType, r.bizId, r.ctAt, r.counted, r.opBy, r.opAt, r.delFlag, c.name currencyName,u.nickName,u.userName,u.phone " +
                "FROM user_account_record r " +
                "LEFT JOIN sys_currency c on c.id = r.currencyId " +
                "LEFT JOIN user u on u.id = r.userId " +
                "$condition ");
        Cnd cnd = Cnd.NEW();
        if (Strings.isNotBlank(currencyId)) {
            cnd.and("r.currencyId", "=", currencyId);
        }
        if (Strings.isNotBlank(keyword)) {
            cnd.and(Cnd.exps("u.nickName", "=", keyword)
                    .or("u.userName", "=", keyword)
                    .or("u.phone", "=", keyword)
            );
        }
        if (bizType != null) {
            cnd.and("r.bizType", "=", bizType);
        }
        if (searchDate != null && searchDate.length > 0) {
            cnd.and("r.opAt", ">=", Times.d2TS(Times.D(searchDate[0])));
            cnd.and("r.opAt", "<=", Times.d2TS(Times.D(searchDate[1])));
        }
        sql.setCondition(cnd);
        sql.appendSourceSql("order by r.ctAt desc");
        return dbService.listPage(AccountRecordVo.class, pageNumber, pageSize, sql.toString());
    }

    /**
     * 根据操作类型与用户id查询流水金额总数
     * <p>
     * 全部换算成 USD
     *
     * @param bizType
     * @param userId
     * @return
     */
    @Override
    public BigDecimal sumAmountByBizTypeAndUserId(BizTypeEnum bizType, String userId) {
        StringBuffer sb = new StringBuffer();
        sb.append("select IFNULL(sum(r.amount * c.rate), 0)");
        sb.append(" from user_account_record r");
        sb.append(" left join sys_currency c on r.currencyId = c.id");
        sb.append(" where 1=1");
        if (bizType.equals(BizTypeEnum.WITHDRAW)) {
            sb.append(" and r.bizType in( 'WITHDRAW','WITHDRAW_REJECT')");
        } else if (bizType.equals(BizTypeEnum.INVEST)) {
            sb.append(" and r.bizType in( 'INVEST','INVEST_REVERT')");
        } else {
            sb.append(" and r.bizType='").append(bizType).append("'");
        }
        sb.append(" and r.userId = '").append(userId).append("'");

        Sql sql = Sqls.create(sb.toString());
        sql.setCallback(Sqls.callback.str());
        this.dao().execute(sql);
        return new BigDecimal(sql.getString());
    }

    @Override
    public Pagination queryWithdrawAndRecharge(String appid, String currencyId, int pageNumber, int pageSize) {
        Sql sql = Sqls.create("SELECT orders.*,sc.NAME currencyName,sc.logo currencyLogo FROM" +
                "\t(" +
                "\tSELECT r.id,r.NO,r.`amount`,'recharge' AS `type`,r.ctAt,r.`status`," +
                "\t\tr.currencyId FROM user_recharge r " +
                "\tWHERE userId = @userId UNION ALL" +
                "\tSELECT c.id,c.NO,c.amount,'withdraw' AS `type`,c.ctAt,c.`status`,c.currencyId \tFROM user_withdraw c " +
                "\tWHERE userId = @userId " +
                "\t) orders LEFT JOIN sys_currency sc ON sc.id = orders.currencyId");
        if (Strings.isNotBlank(currencyId)) {
            sql.appendSourceSql("where orders.currencyId = @currencyId");
            sql.setParam("currencyId", currencyId);
        }
        sql.setParam("userId", appid);
        sql.appendSourceSql("ORDER BY ctAt DESC");
        String sqlStr = sql.toString();
        return dbService.listPage(WithdrawAndRechargeVO.class, pageNumber, pageSize, sqlStr);
    }

}
